/*
 * 一个简单的测试解码视频流的程序
 */

#include "testplayvideo.h"

#include <stdio.h>
#include <stdlib.h>
#include <QDebug>

#ifdef __cplusplus
#define __STDC_CONSTANT_MACROS
#define UINT64_C
extern "C"
{
#endif
#include "libavcodec/avcodec.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/ffversion.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libpostproc/postprocess.h"
#include <libavutil/imgutils.h>
#ifdef __cplusplus
}
#endif

//将FFmpeg解码后的每个帧的数据转成RGB32像素格式后保存到本地文件
void saveFrame(AVFrame *pFrame, int width, int height, int iFrame)
{
    FILE *pFile;
    char szFilename[32];
    int y;

    // 打开文件
    sprintf(szFilename, "frame%d.ppm", iFrame);
    pFile = fopen(szFilename, "wb");
    if (pFile == NULL)
        return;

    // 写入文件头
    fprintf(pFile, "P6\n%d %d\n255\n", width, height);

    // 写入像素数据
    for (y = 0; y < height; y++)
        fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);

    // 关闭文件
    fclose(pFile);
}

int main_decode()
{
    char filePath[] = "zhy.flv"; //文件地址
    int  videoStreamIndex = -1;  //视频流所在流序列中的索引
    int ret=0; //默认返回值

    //用于保存解码后的帧的原始像素格式数据的文件，此文件可用ffplay播放。
    FILE *fp=fopen("result.yuv","w+b");
    if(fp==NULL){
        printf("Cannot open file.\n");
        return -1;
    }

    //需要的变量名并初始化
    AVFormatContext *fmtCtx=NULL;
    AVPacket *pkt =NULL;
    AVCodecContext *codecCtx=NULL;
    AVCodecParameters *avCodecPara=NULL;
    AVCodec *codec=NULL;
    AVFrame *yuvFrame = av_frame_alloc();
    AVFrame *rgbFrame = av_frame_alloc();

    //分配一个AVFormatContext，FFMPEG所有的操作都要通过这个AVFormatContext来进行
    fmtCtx = avformat_alloc_context();

    //打开文件
    if ((ret=avformat_open_input(&fmtCtx, filePath, NULL, NULL)) != 0) {
        printf("cannot open video file\n");
        return -1;
    }

    //获取流信息
    if ((ret=avformat_find_stream_info(fmtCtx, NULL)) < 0) {
        printf("cannot retrive video info\n");
        return -2;
    }

    //循环查找视频中包含的流信息，直到找到视频类型的流，将其记录到videoStreamIndex变量中
    for(unsigned int i = 0; i < fmtCtx->nb_streams; i++) {
        if(fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStreamIndex = i;
            break; //找到视频流就退出
        }
    }

    //如果videoStream为-1 说明没有找到视频流
    if (videoStreamIndex == -1) {
        printf("cannot find video stream\n");
        return -3;
    }

    //打印输入和输出信息：长度 比特率 流格式等
    av_dump_format(fmtCtx, 0, filePath, 0);

    //查找解码器
    avCodecPara = fmtCtx->streams[videoStreamIndex]->codecpar;
    codec = avcodec_find_decoder(avCodecPara->codec_id);
    if (codec == NULL) {
        printf("cannot find decoder\n");
        return -4;
    }

    //根据解码器参数来创建解码器内容
    codecCtx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codecCtx, avCodecPara);
    if (codecCtx == NULL) {
        printf("Cannot alloc context.");
        return -5;
    }

    //打开解码器
    if ((ret=avcodec_open2(codecCtx, codec, NULL)) < 0) {
        printf("cannot open decoder\n");
        return -6;
    }

    //设置数据转换参数,用于后面sws_scale函数进行像素转换时指定转换格式参数。
    //yuvFrame是解码后的帧，像素格式是AVCodecContext 结构体中的 pix_fmt 成员，
    //可能是YUV420P等其它格式，这里转成RGB32格式。
    //yuvFrame --> rgbFrame  像素格式转换。
    struct SwsContext *img_ctx = sws_getContext(
                codecCtx->width, codecCtx->height, codecCtx->pix_fmt, //源地址长宽以及数据格式
                codecCtx->width, codecCtx->height, AV_PIX_FMT_RGB32,  //目的地址长宽以及数据格式
                SWS_BICUBIC, NULL, NULL, NULL);                       //算法类型  AV_PIX_FMT_YUVJ420P   AV_PIX_FMT_BGR24

    //分配空间,一帧图像数据大小
    //解码转换像素格式后的视频帧rgbFrame需要一个缓存buffer，为什么？
    //下一行代码将rgbFrame和缓存buffer关联。
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32, codecCtx->width, codecCtx->height, 1);
    unsigned char *out_buffer = (unsigned char *)av_malloc(numBytes * sizeof(unsigned char));

    //会将rgbFrame的数据按RGB格式自动"关联"到buffer
    //即rgbFrame中的数据改变了,out_buffer中的数据也会相应的改变
    av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, out_buffer, AV_PIX_FMT_RGB32,
                         codecCtx->width, codecCtx->height, 1);

    //分配AVPacket结构体
    pkt = av_packet_alloc(); //分配一个packet
    av_new_packet(pkt, codecCtx->width * codecCtx->height); //调整packet的数据

    //读取视频信息
    int i = 0; //用于帧计数
    int w=codecCtx->width;//视频宽度
    int h=codecCtx->height;//视频高度
    while (av_read_frame(fmtCtx, pkt) >= 0)
    {
        if (pkt->stream_index == videoStreamIndex){
            if (avcodec_send_packet(codecCtx, pkt) == 0){
                while (avcodec_receive_frame(codecCtx, yuvFrame) == 0)
                {
                    //将解码后帧的数据（YUV420P）写入yuv文件中
                    //ffplay播放此yuv文件：ffplay -pixel_format yuv420p -s 1280x534 result.yuv
                    //尺寸大小1280*534，实际见打印出的视频信息
                    for(int j=0; j<yuvFrame->height; j++)
                    fwrite(yuvFrame->data[0] + j * yuvFrame->linesize[0], 1, yuvFrame->width, fp);
                    for(int j=0; j<yuvFrame->height/2; j++)
                    fwrite(yuvFrame->data[1] + j * yuvFrame->linesize[1], 1, yuvFrame->width/2, fp);
                    for(int j=0; j<yuvFrame->height/2; j++)
                    fwrite(yuvFrame->data[2] + j * yuvFrame->linesize[2], 1, yuvFrame->width/2, fp);

                    //保存每帧转为RGB32后的数据,保存每帧转为RGB32后的数据，一帧一个文件，不能播放。
                    if (++i <= 5000 && i >= 4995){
                        sws_scale(img_ctx,
                                  (const uint8_t* const*)yuvFrame->data,
                                  yuvFrame->linesize,
                                  0,
                                  codecCtx->height,
                                  rgbFrame->data,
                                  rgbFrame->linesize);
                        saveFrame(rgbFrame, codecCtx->width, codecCtx->height, i);
                    }
                }
            }
        }
        av_packet_unref(pkt); //重置pkt的内容
    }
    qDebug()<<"Total frames: "<<i;

    //释放所有指针
    av_packet_free(&pkt);
    sws_freeContext(img_ctx);
    av_free(out_buffer);
//    avcodec_parameters_free(&avCodecPara); //加了会报重复释放。
    av_frame_free(&yuvFrame);
    av_free(rgbFrame);
    avcodec_close(codecCtx);
    avformat_close_input(&fmtCtx);
    qDebug()<<"End --------------------- ";

    return 0;
}
